Raft Part I - Leader Election大选

Resource:

刚刚花了4天时间结束了Raft共识算法的第一个checkpoint,这里做一个总结。Raft 将会分为两个部分,在第一个部分中,我会通过go实现leader election并讲解核心测试设计。在美国2024大选之际,正好写一个民主选举的项目哈哈

核心思路

首先我们需要服务器的的3个状态: leader, follower, and candidate,在文章中:

作为程序设计,我将这个部分更具体的表示成 run() 函数的几个通道接受信号后的表示:

实现细节

run() 函数

run函数在初始化一个服务器的时候就会被go routine运行,此后,他一直作为一个中心接受各种输入。

followerClock()函数

它负责管理 Raft 节点在追随者(follower)状态下的计时器,用于检测leader的heartbeat。如果在指定的超时时间内没有收到heartbeat,节点将变异成Candidate,启动新的选举过程。

election()函数

启动election函数之后:

  1. identity变成Candidate
  1. currentTerm++, 版本更新
  1. voteFor = me,为自己投票

启动三个函数

go rf.electionClock(stopElectionClockCh)
go rf.elecParallelSend(stopElecParallelSend)
go rf.electionRecv(stopElectionRecv)

之后接受信号:

elecParallelSend() electionRecv()函数

在文章中提到发送voteRequest的时候需要parallel,这里我们保证concurrent的,逻辑上来说就是对的,所以使用

go rf.sendRequestVote(i, &RequestVoteArgs{
	Term:         curTerm,
	CandidateId:  rf.me,
	LastLogIndex: logLen - 1,
	LastLogTerm:  curTerm,
}, reply)

发送RPC就可以,记得在访问state变量的时候加锁,不然可以会panic其他正在对state改变的操作

在RPC返回的时候我们就会需要立刻返回这个,所以我们构造一个buffer chan来回收每个选民的vote结果

if ok && reply.VoteGranted {rf.voteIn <- true}

随后我们在electionRecv()函数中回收vote := <-rf.voteIn

electionClock()函数

electionClock比起follower clock更加简单,只需要检测是倒计时先结束还是外部把这个go routine先结束就行

RequestVote()

非常重要!!!这个是RPC的handler函数,所以他的正确实现关乎到了整个选举是否可以顺利。首先在整个函数的首尾整个加锁解锁保证在函数投票的时候前后的状态一致。总共有三种情况:

  1. args.Term > rf.state.currentTerm
    • 投票reply.VoteGranted = true
    • 更新Term: rf.state.currentTerm = args.Term
    • 记录这轮把票投给了rf.state.votedFor = args.CandidateId
    • 此时还需分类讨论:
      • 如果我是Candidate
        • 变成 Follower,开始倒计时rf.followerClock()
        • 停止选举 rf.closeElectionCh <- struct{}{}
      • 如果我是Leader
        • 变成Follower,开始倒计时rf.followerClock()
        • 停止heartbeat行为rf.closeHeartbeatClock <- struct{}{}
      • 如果我是Follower
        • 重置倒计时rf.resetFollowerClock <- struct{}{}
  1. args.Term == rf.state.currentTerm
    • 直接不投票就得了,为什么?如果我是follwer也不投票吗?是的,因为如果他是这一轮的leader那么他就应该发你heartbeat而不是发你vote request。他和你一个Term却在选举?他要是合理的选举必须比你大一个term至少!
  1. args.Term < rf.state.currentTerm
    • 不给他投票且告知他reply.Term = rf.state.currentTerm他已经落后一个term了

heartbeat(), sendHeartbeat()函数

很简单,倒计时到了就重新倒计时并使用一个go routine来go sendHeartbeat()

sendHeartbeat() 很简单对所有server RCPgo sendAppendEntries()

AppendEntries()函数

很重要!!!和RequestVote类似可以分三类

在开始前加锁,在结束前解锁

将heartbeatTerm发送到 run 中,进一步处理。 rf.heartbeatIn <- args.Term

测试设计

TestInitialElection2A

TestReElection2A

TestReElectionHidden2A

TestSmallPartitionConsensusHidden2A

TestLeaderConsistencyHidden2A